/*
 * Copyright (C) 2016 Lokiy(liulongke@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  　　　　http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

@file:JvmName("Types")
@file:Suppress("unused")

package com.lokiy.kit.ktx

import com.google.gson.reflect.TypeToken
import java.io.Serializable
import java.lang.reflect.*
import java.util.*
import kotlin.collections.ArrayList

/**
 * Types
 * @author Lokiy
 * 2019-04-23 19:57
 */
inline fun <reified T> typeOf(): Type = object : TypeToken<T>() {}.type

inline fun <reified T> typeOfList(): Type = ArrayList::class.java.toParameterizedType(T::class.java)

//inline fun <reified T> typeOfPage(): Type = getParameterizedType(Page::class.java, T::class.java)

//inline fun <reified T> typeOfResult(): Type = getParameterizedType(BaseRespBody::class.java, T::class.java)

internal val EMPTY_TYPE_ARRAY = arrayOf<Type>()

fun Type.toParameterizedType(type: Type): ParameterizedTypeImpl {
    return ParameterizedTypeImpl(null, this, type)
}

/**
 * Returns the type from super class's type parameter in [ canonical form][canonicalize].
 */
@JvmOverloads
fun Class<*>.getSuperclassTypeParameter(index: Int = 0): Type {
    val superclass = genericSuperclass
    if (superclass is Class<*>) {
        throw RuntimeException("Missing type parameter.")
    }
    val parameterized = superclass as ParameterizedType
    return canonicalize(parameterized.actualTypeArguments[index])
}

/**
 * Returns a type that is functionally equal but not necessarily equal
 * according to [Object.equals()][Object.equals]. The returned
 * type is [Serializable].
 */
private fun canonicalize(type: Type): Type {
    return if (type is Class<*>) {
        if (type.isArray) GenericArrayTypeImpl(canonicalize(type.componentType!!)) else type

    } else if (type is ParameterizedType) {
        ParameterizedTypeImpl(type.ownerType, type.rawType, *type.actualTypeArguments)

    } else if (type is GenericArrayType) {
        GenericArrayTypeImpl(type.genericComponentType)

    } else if (type is WildcardType) {
        WildcardTypeImpl(type.upperBounds, type.lowerBounds)

    } else {
        // type is either serializable as-is or unsupported
        type
    }
}

private fun typeToString(type: Type): String {
    return if (type is Class<*>) type.name else type.toString()
}

private fun checkNotPrimitive(type: Type) {
    checkArgument(type !is Class<*> || !type.isPrimitive)
}

private fun checkArgument(condition: Boolean) {
    require(condition)
}

private fun hashCodeOrZero(o: Any?): Int {
    return o?.hashCode() ?: 0
}

private fun <T> checkNotNull(obj: T?): T {
    if (obj == null) {
        throw NullPointerException()
    }
    return obj
}

/**
 * Returns true if `a` and `b` are equal.
 */
private fun equals(a: Type, b: Type): Boolean {
    return if (a === b) {
        // also handles (a == null && b == null)
        true

    } else if (a is Class<*>) {
        // Class already specifies equals().
        a == b

    } else if (a is ParameterizedType) {
        if (b !is ParameterizedType) {
            false
        } else a.ownerType == b.ownerType && a.rawType == b.rawType && Arrays.equals(a.actualTypeArguments, b.actualTypeArguments)

        // TODO: save a .clone() call

    } else if (a is GenericArrayType) {
        if (b !is GenericArrayType) {
            false
        } else equals(a.genericComponentType, b.genericComponentType)

    } else if (a is WildcardType) {
        if (b !is WildcardType) {
            false
        } else Arrays.equals(a.upperBounds, b.upperBounds) && Arrays.equals(a.lowerBounds, b.lowerBounds)

    } else if (a is TypeVariable<*>) {
        if (b !is TypeVariable<*>) {
            false
        } else a.genericDeclaration === b.genericDeclaration && a.name == b.name

    } else {
        // This isn't a type we support. Could be a generic array type, wildcard type, etc.
        false
    }
}

internal fun equal(a: Any?, b: Any?): Boolean {
    return a == b
}

class ParameterizedTypeImpl(ownerType: Type?, rawType: Type, vararg typeArguments: Type) : ParameterizedType, Serializable {
    private val ownerType: Type?
    private val rawType: Type
    private val typeArguments: Array<Type>

    init {
        // require an owner type if the raw type needs it
        if (rawType is Class<*>) {
            val rawTypeAsClass = rawType
            val isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.modifiers) || rawTypeAsClass.enclosingClass == null
            checkArgument(ownerType != null || isStaticOrTopLevelClass)
        }

        this.ownerType = if (ownerType == null) null else canonicalize(ownerType)
        this.rawType = canonicalize(rawType)
        @Suppress("UNCHECKED_CAST")
        this.typeArguments = typeArguments.clone() as Array<Type>
        for (t in this.typeArguments.indices) {
            checkNotNull(this.typeArguments[t])
            checkNotPrimitive(this.typeArguments[t])
            this.typeArguments[t] = canonicalize(this.typeArguments[t])
        }
    }

    override fun getActualTypeArguments(): Array<Type> {
        return typeArguments.clone()
    }

    override fun getOwnerType(): Type? {
        return ownerType
    }

    override fun getRawType(): Type {
        return rawType
    }

    override fun equals(other: Any?): Boolean {
        return other is ParameterizedType && equals(this, other)
    }

    override fun hashCode(): Int {
        return typeArguments.contentHashCode() xor rawType.hashCode() xor hashCodeOrZero(ownerType)
    }

    override fun toString(): String {
        val stringBuilder = StringBuilder(30 * (typeArguments.size + 1))
        stringBuilder.append(typeToString(rawType))

        if (typeArguments.isEmpty()) {
            return stringBuilder.toString()
        }

        stringBuilder.append("<").append(typeToString(typeArguments[0]))
        for (i in 1 until typeArguments.size) {
            stringBuilder.append(", ").append(typeToString(typeArguments[i]))
        }
        return stringBuilder.append(">").toString()
    }

    companion object {
        private const val serialVersionUID: Long = 0
    }


}

private class GenericArrayTypeImpl(componentType: Type) : GenericArrayType, Serializable {
    private val componentType: Type = canonicalize(componentType)

    override fun getGenericComponentType(): Type {
        return componentType
    }

    override fun equals(other: Any?): Boolean {
        return other is GenericArrayType && equals(this, other)
    }

    override fun hashCode(): Int {
        return componentType.hashCode()
    }

    override fun toString(): String {
        return typeToString(componentType) + "[]"
    }

    companion object {
        private const val serialVersionUID: Long = 0
    }


}

/**
 * The WildcardType interface supports multiple upper bounds and multiple
 * lower bounds. We only support what the Java 6 language needs - at most one
 * bound. If a lower bound is set, the upper bound must be Object.class.
 */
private class WildcardTypeImpl(upperBounds: Array<Type>, lowerBounds: Array<Type>) : WildcardType, Serializable {
    private val upperBound: Type
    private val lowerBound: Type?

    init {
        checkArgument(lowerBounds.size <= 1)
        checkArgument(upperBounds.size == 1)

        if (lowerBounds.size == 1) {
            checkNotNull(lowerBounds[0])
            checkNotPrimitive(lowerBounds[0])
            checkArgument(upperBounds[0] === Any::class.java)
            this.lowerBound = canonicalize(lowerBounds[0])
            this.upperBound = Any::class.java

        } else {
            checkNotNull(upperBounds[0])
            checkNotPrimitive(upperBounds[0])
            this.lowerBound = null
            this.upperBound = canonicalize(upperBounds[0])
        }
    }

    override fun getUpperBounds(): Array<Type> {
        return arrayOf(upperBound)
    }

    override fun getLowerBounds(): Array<Type> {
        return if (lowerBound != null) arrayOf(lowerBound) else EMPTY_TYPE_ARRAY
    }

    override fun equals(other: Any?): Boolean {
        return other is WildcardType && equals(this, other)
    }

    override fun hashCode(): Int {
        // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());
        return (if (lowerBound != null) 31 + lowerBound.hashCode() else 1) xor 31 + upperBound.hashCode()
    }

    override fun toString(): String {
        return when {
            lowerBound != null -> {
                "? super " + typeToString(lowerBound)
            }
            upperBound === Any::class.java -> {
                "?"
            }
            else -> {
                "? extends " + typeToString(upperBound)
            }
        }
    }

    companion object {
        private const val serialVersionUID: Long = 0
    }


}
